
#include "precompiled.h"
#include "scriptenvironmentex.h"
#include <QtGui>
#include <QtScriptTools>
#include "helper.h"
#include <zones.h>
#include <engine.h>
#include <extensions/zoneinterface.h>
#include "windowsmanager.h"
#include "inputdata.h"
#include "objects/optra.h"

#include <scripting/system/io/file.h>

QScriptValue windowsManagerToScriptValue(QScriptEngine *engine, WindowsManager* const &in)
{ return engine->newQObject(in); }

void windowsManagerFromScriptValue(const QScriptValue &object, WindowsManager* &out)
{ out = qobject_cast<WindowsManager*>(object.toQObject()); }




QScriptValue script_input(QScriptContext *, QScriptEngine *);
QScriptValue script_output(QScriptContext *, QScriptEngine *);
QScriptValue script_help(QScriptContext *, QScriptEngine *);
QScriptValue script_window(QScriptContext *, QScriptEngine *);
QScriptValue script_include(QScriptContext *, QScriptEngine *);


class ScriptEnvironmentExPrivate{
public:
    ScriptEnvironmentExPrivate():
            engine(0)
            ,debugger(0)
            ,debugWindow(0)
            ,initialized(false)
    {}
    ~ScriptEnvironmentExPrivate()
    {
        delete engine;
        delete debugger;
        delete debugWindow;
    }
    QScriptEngine* engine;
    QScriptEngineDebugger* debugger;
    QMainWindow* debugWindow;
    QScriptProgram program;
    QString errorString;
    bool initialized;
};

ScriptEnvironmentEx::ScriptEnvironmentEx(ScriptEnvironmentMode mode, Opl::Core::EntityObject *parent) :
        Opl::Scripting::ScriptEnvironment(mode, parent), O_PRIVATE_CREATE(ScriptEnvironmentEx)
{


}

//QScriptValue construct_InputData(QScriptContext *, QScriptEngine *engine) {
//    return engine->newQObject(new InputData(InputData::user));
//}

template <class T> void registerScriptClass(QScriptEngine* engine, QString name)
{
    qScriptRegisterMetaType(engine, T::toScriptValue, T::fromScriptValue);
    QScriptValue constructor = engine->newFunction(T::construct_Script);
    QScriptValue value = engine->newQMetaObject(&T::staticMetaObject, constructor);
    LI.Debug() << "Register Class:" << name;
    engine->globalObject().setProperty(name, value);
}

template <class T> void registerScriptClass(QScriptEngine* engine)
{
    registerScriptClass<T>(engine, T::ID);
}

void ScriptEnvironmentEx::initialize()
{
    O_D(ScriptEnvironmentEx);

    if (d->initialized)
        return;

    d->engine = new QScriptEngine;
    connect(d->engine, SIGNAL(signalHandlerException(QScriptValue)), this, SLOT(signalHandlerException(QScriptValue)));
    connect(this, SIGNAL(error(QScriptValue)), this, SLOT(errorHandler(QScriptValue)));

    qScriptRegisterMetaType(d->engine, windowsManagerToScriptValue, windowsManagerFromScriptValue);


    registerScriptClass<Opl::Core::EntityObject>(d->engine);
    registerScriptClass<InputData>(d->engine);

    registerScriptClass<Opl::Scripting::System::IO::File>(d->engine);

    d->debugger = new QScriptEngineDebugger(0);

    registerFunction("input", script_input, "", this);
    registerFunction("print", script_output, "", this);
    registerFunction("help", script_help, "", this);
    registerFunction("include", script_include, "", this);
    //registerFunction("scene", script_window);

    registerObject("optra", new OptraCore(0, 0));
    Zones::ZonesInterfaces interfaces = Engine::instance()->zones()->interfaces();

    for (int i = 0; i < interfaces.count(); i++){
        QString namespec = QString("optra.zones.%1").arg(interfaces[i]->name());
        registerObject(namespec, interfaces[i]->object());
    }

    d->initialized = true;
}

ScriptEnvironmentEx::~ScriptEnvironmentEx()
{
    O_PRIVATE_DELETE;
}

void ScriptEnvironmentEx::signalHandlerException(const QScriptValue & exception)
{
    LI.Debug() << QString("Script error: %1").arg(exception.toString());
    emit error(exception);
}

void ScriptEnvironmentEx::registerObject(const QString& name, Opl::Core::EntityObject* object, const QString& package)
{
    O_D(ScriptEnvironmentEx);
    QStringList part = package.split(".", QString::SkipEmptyParts);

    QScriptValue globalObject = d->engine->globalObject();
    QScriptValue co = globalObject;
    foreach(QString p, part){
        if (!co.property(p).isObject()){
            QScriptValue o = d->engine->newObject();
            co.setProperty(p, o);
            co = o;
        }
    }
    QScriptValue obj = d->engine->newQObject(object);
    co.setProperty(name, obj);
}


void ScriptEnvironmentEx::registerFunction(const QString& name, ScriptFunction function, const QString& package, QObject* data)
{
    O_D(ScriptEnvironmentEx);
    QStringList part = package.split(".", QString::SkipEmptyParts);

    QScriptValue globalObject = d->engine->globalObject();
    QScriptValue co = globalObject;
    foreach(QString p, part){
        if (!co.property(p).isObject()){
            QScriptValue o = d->engine->newObject();
            co.setProperty(p, o);
            co = o;
        }
    }

    QScriptValue fun = d->engine->newFunction(function);
    fun.setData(d->engine->newQObject(data));
    co.setProperty(name, fun);
}

const QString& ScriptEnvironmentEx::errorString()const
{
    O_C(ScriptEnvironmentEx);
    return d->errorString;
}

void ScriptEnvironmentEx::errorHandler(const QScriptValue & exception)
{
    O_D(ScriptEnvironmentEx);
    d->errorString = exception.toString();
}

bool ScriptEnvironmentEx::exec(const QString& script, bool debug)
{
    return exec(script, QString(), debug);
}

bool ScriptEnvironmentEx::exec(const QString& script, const QString& filename /*= QString()*/, bool debug/* = false*/)
{

    O_D(ScriptEnvironmentEx);
    QScriptSyntaxCheckResult syntax = QScriptEngine::checkSyntax(script);
    if (syntax.state() == QScriptSyntaxCheckResult::Error){
        QString msg = QString("Syntax Error:%1: %2").arg(syntax.errorLineNumber()).arg(syntax.errorMessage());        
        LI.Debug() << msg;
        emit error(QScriptValue(msg));
        return false;
    }

    d->engine->clearExceptions();
    QTime operationTime;
    operationTime.start();

    if (debug){
        d->debugWindow = d->debugger->standardWindow();
        d->debugWindow->setWindowModality(Qt::ApplicationModal);
        d->debugWindow->resize(1280, 704);
        d->debugger->attachTo(d->engine);
        d->debugger->action(QScriptEngineDebugger::InterruptAction)->trigger();
    }else{
        d->debugger->detach();
    }

    d->program = QScriptProgram(script);
    QScriptValue res = d->engine->evaluate(script, filename);
    if (res.isError()){
        LI.Debug() << res.toString();
        emit error(res);
        return false;
    }    

    LI.Debug() << operationTime.toString();
    return true;
}

void ScriptEnvironmentEx::printl(const QString& out)
{
    print(QString("%1\n").arg(out));
}

void ScriptEnvironmentEx::print(const QString& out)
{
    emit output(out);
}


QScriptValue script_include(QScriptContext *context, QScriptEngine *engine)
{
    for (int i = 0; i < context->argumentCount(); ++i) {       

        QString fileName = Engine::instance()->environment()->findFile(context->argument(0).toString());
        QFile file(fileName);
        if (!file.open(QIODevice::ReadOnly))
            return context->throwError(QString::fromLatin1("could not open %0 for reading").arg(fileName));
        QTextStream ts(&file);
        QString contents = ts.readAll();
        file.close();
        QScriptContext *pc = context->parentContext();
        context->setActivationObject(pc->activationObject());
        context->setThisObject(pc->thisObject());
        QScriptValue ret = engine->evaluate(contents);
        if (engine->hasUncaughtException())
            return ret;
    }
    return engine->undefinedValue();
}

QScriptValue script_input(QScriptContext *context, QScriptEngine *engine)
{
    QString result;
    for (int i = 0; i < context->argumentCount(); ++i) {
        if (i > 0)
            result.append(" ");
        result.append(context->argument(i).toString());
    }

    QScriptValue calleeData = context->callee().data();
    ScriptEnvironmentEx *par = qobject_cast<ScriptEnvironmentEx*>(calleeData.toQObject());
    par->print(result);
    return engine->undefinedValue();
}

QScriptValue script_output(QScriptContext *context, QScriptEngine *engine)
{
    QString result;
    for (int i = 0; i < context->argumentCount(); ++i) {
        if (i > 0)
            result.append(" ");
        result.append(context->argument(i).toString());
    }

    QScriptValue calleeData = context->callee().data();
    ScriptEnvironmentEx *par = qobject_cast<ScriptEnvironmentEx*>(calleeData.toQObject());
    par->print(result);
    return engine->undefinedValue();
}

QScriptValue script_help(QScriptContext *context, QScriptEngine *engine)
{
    QString result;
    QScriptValue calleeData = context->callee().data();
    ScriptEnvironmentEx *par = qobject_cast<ScriptEnvironmentEx*>(calleeData.toQObject());

    par->print("help:\n");
    par->print("zones:\n");
    const Zones::ZonesInterfaces& e = Engine::instance()->zones()->interfaces();
    foreach(Optra::Extensions::ZoneInterface* p, e){
        par->printl(QString("\t%1").arg(p->name()));
    }
    return engine->undefinedValue();
}

//QScriptValue script_window(QScriptContext *context, QScriptEngine *engine)
//{
//    QGraphicsView* vi = new QGraphicsView(new QGraphicsScene);
//    vi->show();
//    return dengine->newQObject(vi->scene());
//}
